﻿using System;

namespace paiban
{
    using paiban.data;
    using System.Linq;
    using xlstool;

    public class PaibanCalc // 排班 计算
    {
        internal WorkPersonInfo workperson;
        internal int work_count;
        internal bool norepeat_mode;
        internal bool avg_mode;
        internal WorkTable table;
        internal int min_onework;
        internal int min_totalwork;
        internal List<string> testedlist;
        internal bool success;
        internal WorkTableThumb thumb;
        internal int calc_times = 0;
        internal DasaoCalc dasaoCalc;
        public PaibanCalc(WorkPersonInfo workperson)
        {
            dasaoCalc = new DasaoCalc(workperson);
            testedlist = new List<string>();
            this.workperson = workperson;

            work_count = (int)cgsys.config.Number("每天工作项目数目");
            avg_mode = cgsys.config.YesOrNo("平均化模式");
            norepeat_mode = cgsys.config.YesOrNo("存在不重复列");
            min_onework = 1;
            min_totalwork = 1;
            table = new WorkTable(workperson, work_count);
            thumb = new WorkTableThumb(table);

            if (avg_mode)
            {
                min_onework = workperson.days / workperson.work_person_count;
                min_totalwork = table.work_table_count / work_count;
            }

            init_fill_data();
        }

        internal void init_fill_data() // 填入默认数据
        {
            int ix = 0;
            foreach (var day in workperson.LoopDays())
            {
                table.Goto(day);
                ix = 0;

                while (ix < work_count)
                    foreach (var person in workperson.ReadWorkPerson(day))
                    {
                        table.Edit(ix++, person);
                        if (ix == work_count) break;
                    }
            }
            remember_now();
            Check();
        }

        public void Calc_01()
        {
            var hash = string.Empty;
            while (true)
            {
                CALC_01_BEGIN:
                calc_times++;
                foreach (var person in workperson.LoopWorkPersons())
                {
                    foreach (var workno in table.LoopWorkNo())
                    {
                        if (thumb.Read(person, workno) <= min_onework) continue; // 忽略过少， workno 就过多

                        foreach (var workno2 in table.LoopWorkNo())
                        {
                            if (workno == workno2) continue;
                            if (thumb.Read(person,workno2) >= min_onework) continue;  // 忽略过多， workno2 就过少

                            foreach (var day in table.WorkDays(workno, person))
                            {
                                table.Goto(day);
                                var person2 = table.Read(workno2);
                                if (person2 == person) continue;

                                table.Edit(workno, person2);
                                table.Edit(workno2, person);
                                if (is_remembered())
                                {   //还原
                                    table.Edit(workno2, person2);
                                    table.Edit(workno, person);
                                    continue;
                                }

                                remember_now();
                                if (Check()) return;
                                goto CALC_01_BEGIN;
                            }
                        }
                    }
                }
                if (hash == table.CalcHash()) break;
                hash = table.CalcHash();
            }
        }
        public void Calc_02()
        {
            var hash = string.Empty;
            while (true)
            {
                CALC_02_BEGIN:
                calc_times++;
                foreach (var person in workperson.LoopWorkPersons())
                {
                    if (thumb.ReadSum(person) >= min_totalwork) continue;

                    foreach (var day in workperson.LoopDays())
                    {
                        if (!table.HasPerson(day, person)) continue;

                        // 存在不全的person
                        foreach (var person2 in table.ReadPerson(day))
                        {
                            if (person2 == person) continue;
                            if (table.ReadCount(day, person2) == 1) continue;
                            if (thumb.ReadSum(person2) <= min_totalwork) continue;

                            foreach (var workno in table.ReadPersonWorkNo(day, person2))
                            {
                                table.Goto(day);

                                table.Edit(workno, person);
                                if (is_remembered())
                                {
                                    table.Edit(workno, person2);
                                    continue;
                                }

                                remember_now();
                                if (Check()) return;
                                goto CALC_02_BEGIN;
                            }
                        }
                    }
                }

                if (hash == table.CalcHash()) break;
                hash = table.CalcHash();
            }
        }
        public void Calc_03()
        {
            var hash = string.Empty;
            while (true)
            {
            CALC_01_BEGIN:
                calc_times++;
                foreach (var person in workperson.LoopWorkPersons())
                {
                    foreach (var workno in table.LoopWorkNo())
                    {
                        if (thumb.Read(person, workno) <= min_onework+1) continue; // 忽略过少， workno 就过多

                        foreach (var workno2 in table.LoopWorkNo())
                        {
                            if (workno == workno2) continue;
                            if (thumb.Read(person, workno2) >= min_onework+1) continue;  // 忽略过多， workno2 就过少

                            foreach (var day in table.WorkDays(workno, person))
                            {
                                table.Goto(day);
                                var person2 = table.Read(workno2);
                                if (person2 == person) continue;

                                table.Edit(workno, person2);
                                table.Edit(workno2, person);
                                if (is_remembered())
                                {   //还原
                                    table.Edit(workno2, person2);
                                    table.Edit(workno, person);
                                    continue;
                                }

                                remember_now();
                                if (Check()) return;
                                goto CALC_01_BEGIN;
                            }
                        }
                    }
                }
                if (hash == table.CalcHash()) break;
                hash = table.CalcHash();
            }
        }
        public void Calc_04()
        {
            //最后7天 末尾移动
            var hash = string.Empty;
            while (true)
            {
            CALC_04_BEGIN:
                calc_times++;
                foreach (var day in workperson.LoopLast5Days())
                {
                    foreach (var workno in table.LoopWorkNo())
                    {
                        var man = table.Read(day, workno);
                        if (table.Read(day, workno) == table.Read(day -1, workno)) // 过密移动
                        {
                            var workmans = workperson.ReadWorkPerson(day).Where(_man=> _man == man).ToArray();
                            foreach (var day2 in workperson.LoopBeforeLast5Days())
                            {
                                var man2 = table.Read(day2, workno);
                                if (man2 == man) continue;
                                if (workmans.Contains(man2) && workperson.ReadWorkPerson(day2).Contains(man) && table.ReadPerson(day2).Count(_man => _man == man) < 2)
                                {
                                    table.Goto(day);
                                    table.Edit(workno, man2);
                                    table.Goto(day2);
                                    table.Edit(workno, man);
                                    if (is_remembered())
                                    {   //还原
                                        table.Goto(day);
                                        table.Edit(workno, man);
                                        table.Goto(day2);
                                        table.Edit(workno, man2);
                                        continue;
                                    }

                                    remember_now();
                                    goto CALC_04_BEGIN;
                                }
                            }
                        }
                    }
                }
                if (hash == table.CalcHash()) break;
                hash = table.CalcHash();
            }
        }
        public void Calc()
        {
            var offset = 0;
            while (!success && offset < work_count)
            {
                Calc_01();
                if (!success) Calc_02();

                var deepcount = table.days * table.work_count; 
                for(var i = 0; !success && i < deepcount; i++)
                {
                    if (!success) Calc_01();
                    if (!success) Calc_03();
                }
                offset++;
                if (!success) table.info.MoveBanbiao();
                if (!success) init_fill_data();
            }

            if (!success) Calc_02();
            if (!success) Calc_01();
            Calc_04();
            dasaoCalc.Calc();
        }
        public bool Check()
        {
            foreach (var person in workperson.LoopWorkPersons())
            {
                foreach (var workno in table.LoopWorkNo()) if (CheckPersonByOneWork(workno, person)) return false;
                if (CheckPersonByAllWork(person)) return false;

                if (avg_mode)
                {
                    foreach (var workno in table.LoopWorkNo()) if (table.WorkCount(workno, person) > min_onework+1) return false; //限制除不尽后的非配不均
                }
            }

            if (norepeat_mode) 
                if (!thumb.HasNoRepeat()) return false;

            this.success = true;
            return true;
        }
        public bool CheckPersonByOneWork(int workno, string person)
        {
            return table.WorkCount(workno, person) < min_onework;
        }
        public bool CheckPersonByAllWork(string person)
        {
            return table.WorkCount(person) < min_totalwork;
        }
        internal void remember_now()
        {
            testedlist.Add(table.CalcHash());
            thumb.Update();
        }
        internal bool is_remembered()
        {
            return testedlist.Contains(table.CalcHash());
        }
        public void SaveCSV(string csv_name)
        {
            table.SaveCSV(csv_name);
        }
        public void SaveExcel(string name)
        {
            var xls = new Excel2003File();
            table.FillExcelSheet(xls.GetWorkbook().ToSheet($"{table.info.year}年{table.info.month}月排班"));
            thumb.FillExcelSheet(xls.GetWorkbook().ToSheet($"{table.info.year}年{table.info.month}月排班缩略图"));
            dasaoCalc.table.FillExcelSheet(xls.GetWorkbook().ToSheet($"{table.info.year}年{table.info.month}月打扫人员"));
            dasaoCalc.thumb.FillExcelSheet(xls.GetWorkbook().ToSheet($"{table.info.year}年{table.info.month}月打扫人员缩略图"));
            using (var f = File.Create($"./{name}.xls")) xls.ToStream(f);
        }
    }
}
